NOIP2015 普及组

T1:金币

题目描述

国王将金币作为工资,发放给忠诚的骑士。第一天,骑士收到一枚金币;之后两天(第二天和第三天),每天收到两枚金币;之后三天(第四、五、六天),每天收到三枚金币;之后四天(第七、八、九、十天),每天收到四枚金币……;这种工资发放模式会一直这样延续下去:当连续N天每天收到N枚金币后,骑士会在之后的连续 N+1 天里,每天收到 N+1 枚金币。

请计算在前 K 天里,骑士一共获得了多少金币。

输入格式

一个正整数 K ,表示发放金币的天数。

输出格式

一个正整数,即骑士收到的金币数。

输入输出样例

输入样例 #1

6

输出样例 #1

14

输入样例 #2

1000

输出样例 #2

29820

说明/提示

【输入输出样例 1 说明】

骑士第一天收到一枚金币;第二天和第三天,每天收到两枚金币;第四、五、六天,每天收到三枚金币。因此一共收到 1+2+2+3+3+3=14 枚金币。

对于 100\% 的数据, 1 ≤ K ≤ 10,000

NOIP2015 普及组第一题

题解:

​ 本题为一个模拟题。设置一个变量 p 用来记录要发的金币数量与要发的天数。然后将 i 1 枚举到 k ,将 j 1 枚举到 K ,最后输出总钱数就可以。

#include <iostream>
using namespace std;
int main() {
    int k;
    cin >> k;
    int p = 1, ans = 0;
    for (int i = 1; i <= k; i++) {
        i--;
        for (int j = 1; j <= p; j++) {
            i++;
            ans += p;
            if (i == k) {
                cout << ans << endl;
                return 0;
            }
        }
        p++;
    }
    return 0;
}

T2: 扫雷游戏

题目描述

扫雷游戏是一款十分经典的单机小游戏。在 n m 列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格)。玩家翻开一个非地雷格时,该格将会出现一个数字——提示周围格子中有多少个是地雷格。游戏的目标是在不翻出任何地雷格的条件下,找出所有的非地雷格。

现在给出 n m 列的雷区中的地雷分布,要求计算出每个非地雷格周围的地雷格数。

注:一个格子的周围格子包括其上、下、左、右、左上、右上、左下、右下八个方向上与之直接相邻的格子。

输入格式

第一行是用一个空格隔开的两个整数 n m ,分别表示雷区的行数和列数。

接下来 n 行,每行 m 个字符,描述了雷区中的地雷分布情况。字符’*’表示相应格子是地雷格,字符’?’表示相应格子是非地雷格。相邻字符之间无分隔符。

输出格式

输出文件包含 n 行,每行 m 个字符,描述整个雷区。用’*’表示地雷格,用周围的地雷个数表示非地雷格。相邻字符之间无分隔符。

输入输出样例

输入样例 #1

3 3
*??
???
?*?

输出样例 #1

*10
221
1*1

输入样例 #2

2 3
?*?
*??

输出样例 #2

2*1
*21

说明/提示

对于 100\% 的数据, 1≤n≤100, 1≤m≤100

NOIP2015 普及组第二题

题解:

​ 本题直接模拟就可以。

​ 我们枚举每个点,如果这个点为 '*' 的话,那么便输出 '*' ,否则找这个点上、下、左、右、左上、右上、左下、右下八个方向雷的总数,并且输出这个数量就可以。

#include <iostream>
using namespace std;
char minefield[102][102]; //存储雷区
int find (int i, int j) { //寻找雷
    int ans = 0;
    for (int i1 = i - 1; i1 <= i + 1; i1++)
        for (int j1 = j - 1; j1 <= j + 1; j1++)
            if (minefield[i1][j1] == '*')
                ans++;
    return ans;
}
int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> minefield[i][j];
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (minefield[i][j] == '*')
                cout << "*";
            else
                cout << find(i, j);
        }
        cout << endl;
    }
    return 0;
}

T3:求和

题目描述

一条狭长的纸带被均匀划分出了 n 个格子,格子编号从 1 n 。每个格子上都染了一种颜色 color\_i [1,m] 当中的一个整数表示),并且写了一个数字 number\_i

定义一种特殊的三元组: (x,y,z) ,其中 x,y,z 都代表纸带上格子的编号,这里的三元组要求满足以下两个条件:

  1. xyz 是整数, x<y<z,y-x=z-y

  2. colorx=colorz 满足上述条件的三元组的分数规定为 (x+z) \times (number\_x+number\_z)

整个纸带的分数规定为所有满足条件的三元组的分数的和。这个分数可能会很大,你只要输出整个纸带的分数除以 10,007 所得的余数即可。

输入格式

第一行是用一个空格隔开的两个正整数 n m,n 表纸带上格子的个数, m 表纸带上颜色的种类数。

第二行有 n 用空格隔开的正整数,第 i 数字 number 表纸带上编号为 i 格子上面写的数字。

第三行有 n 用空格隔开的正整数,第 i 数字 color 表纸带上编号为 i 格子染的颜色。

输出格式

一个整数,表示所求的纸带分数除以 10007 所得的余数。

输入输出样例

输入样例 #1

6 2
5 5 3 2 2 2
2 2 1 1 2 1

输出样例 #1

82

输入样例 #2

15 4
5 10 8 2 2 2 9 9 7 7 5 6 4 2 4
2 2 3 3 4 3 3 2 4 4 4 4 1 1 1

输出样例 #2

1388

说明/提示

【输入输出样例 1 说明】

纸带如题目描述中的图所示。

所有满足条件的三元组为: (1, 3, 5), (4, 5, 6)

所以纸带的分数为 (1 + 5) \times (5 + 2) + (4 + 6) \times (2 + 2) = 42 + 40 = 82

对于第 1 组至第 2 组数据, 1 ≤ n ≤ 100, 1 ≤ m ≤ 5

对于第 3 组至第 4 组数据, 1 ≤ n ≤ 3000, 1 ≤ m ≤ 100

对于第 5 组至第 6 组数据, 1 ≤ n ≤ 100000, 1 ≤ m ≤ 100000 ,且不存在出现次数超过 20 的颜色;

对 于 全 部 10 组 数 据 , 1 ≤ n ≤ 100000, 1 ≤ m ≤ 100000, 1 ≤ color\_i ≤ m,1≤number\_i≤100000

NOIP2015 普及组第三题

题解:

​ 直接枚举本题的话是过不了的。因此,我们便考虑用一些玄学的方法来做这道题。

​ 我们只需要枚举 x z 就可以。为什么呢?因为若存在特殊的三元组: (x,y,z) 的话,那么, y=\frac{x+z}{2}

​ 我们在进一步讨论 x z 的问题。首先, x z 必须要颜色相同,因此,我们可以将相同颜色的放个存储在一个相同数组中。其次, x z 的编号可能是一奇一偶吗?我们分析一下,如题目图片,

​ 若 x 编号 \ = 3\ ,\ y 编号 \ =\ 6 的话,那么 y\ =\frac{x+z}{2}\ =\frac{9}{2}\ =\ 4.5 ,因此便不存在 y 了。所以,我们可以相同颜色的再分一下奇偶性,这样又做到了一步优化。

然后,若一个相同颜色相同奇偶性的数组中有 cnt 个元素。因此,这个数组中特殊三元组的个数也就是:

(num[1]\ +\ num[2]) \times (i[1]\ +\ i[2])\ +\ (num[1]\ +\ num[3]) \times (i[1]\ +\ i[3])\ +\ ...+\ (num[1]\ +\ num[cnt\ -\ 1])\ \times\ (i[1]\ +\ i[cnt\ -\ 1])\ +\ (num[1]\ +\ num[cnt])\ \times (i[1]\ +\ i[cnt])

+

(num[2]\ +\ num[3]) \times (i[2]\ +\ i[3])\ +\ (num[2]\ +\ num[4]) \times (i[2]\ +\ i[4])\ +\ ...+\ (num[2]\ +\ num[cnt\ -\ 1])\ \times\ (i[2]\ +\ i[cnt\ -\ 1])\ +\ (num[2]\ +\ num[cnt])\ \times (i[cnt]\ +\ i[cnt])

+

...

+

(num[k\ -\ 1]\ +\ num[k])\ \times\ (i[k\ -\ 1]\ +\ i[k])

拆开上面的式子,也就是:

=

num[1]\ \times\ i[1]\ +\ num[1]\ \times\ i[2]\ +\ num[2]\ \times\ i[1]\ +\ num[2]\ \times\ i[2]\ +\ ... +\ num[1]\ \times\ i[1]\ +\ num[1]\ \times\ i[cnt]\ +\ num[cnt]\ \times\ i[1]\ +\ num[cnt]\ \times\ i[cnt]

...

+

num[k\ -\ 1]\ \times\ i[k\ -\ 1]\ +\ num[k\ -\ 1]\ \times\ i[k]\ +\ num[k]\ \times\ i[k\ -\ 1]\ +\ num[k]\ \times\ i[k]

合并上面的式子:

=

num[1]\ \times\ (i[1]\ +\ i[2]\ +\ i[1]\ +\ i[3]\ +\ …+\ i[1]\ +\ i[cnt])\ +\ num[2]\ \times\ (i[1]\ +\ i[2]\ +\ i[2]\ +\ i[3]\ +\ …+\ i[2]\ +\ i[cnt])

+

+

num[cnt]\ \times\ (i[1]\ +\ i[cnt]\ +\ i[2]\ +\ i[cnt]\ +\ …+\ i[cnt\ -\ 1]\ +\ i[cnt])

进一步化简,得到:

=

num[1]\ \times\ (i[1]\ \times\ (n\ -\ 2)\ +\ i[1]\ +\ i[2]\ +\ …\ +\ i[cnt])\ +\ num[2]\ \times\ (i[2]\ \times\ (n-2)\ +\ i[1]\ +\ i[2]\ +\ …\ +\ i[cnt])

+

+

num[cnt]\ \times\ (i[cnt]\ \times\ (n-2)\ +\ i[1]\ +\ i[2]\ +\ ……\ +\ i[cnt])

​ 因此,我们只需要统计一下 i 的前缀和就可以了,然后带入推出来的式子就 OK 啦!

#include <iostream>
#define MOD 10007
using namespace std;
int number[100001], cnt[100001][2], sum[100001][2], p[100001];
int main() {
    int n, m; //n表纸带上格子的个数,m表纸带上颜色的种类数
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> number[i];
    for (int i = 1; i <= n; i++) {
        cin >> p[i]; //读入标号为 i 的格子的颜色
        cnt[p[i]][i % 2]++;
        sum[p[i]][i % 2] = (sum[p[i]][i % 2] + number[i]) % MOD; //计算前缀和
    }
    int ans = 0;
    for (int i = 1; i <= n; i++)
        ans = ans + i * (number[i] % MOD * (cnt[p[i]][i % 2] - 2) % MOD + sum[p[i]][i % 2]) % MOD;
    cout << ans % MOD << endl;
    return 0;
}

T4:推销员

题目描述

阿明是一名推销员,他奉命到螺丝街推销他们公司的产品。螺丝街是一条死胡同,出口与入口是同一个,街道的一侧是围墙,另一侧是住户。螺丝街一共有 N 家住户,第 i 家住户到入口的距离为 S_i 米。由于同一栋房子里可以有多家住户,所以可能有多家住户与入口的距离相等。阿明会从入口进入,依次向螺丝街的 X 家住户推销产品,然后再原路走出去。

阿明每走 1 米就会积累 1 点疲劳值,向第 i 家住户推销产品会积累 A_i 点疲劳值。阿明是工作狂,他想知道,对于不同的 X ,在不走多余的路的前提下,他最多可以积累多少点疲劳值。

输入格式

第一行有一个正整数 N ,表示螺丝街住户的数量。

接下来的一行有 N 个正整数,其中第 i 个整数 S_i 表示第 i 家住户到入口的距离。数据保证 S_1≤S_2≤…≤S_n<10^8

接下来的一行有 N 个正整数,其中第 i 个整数 A_i 表示向第 i 户住户推销产品会积累的疲劳值。数据保证 A_i<1000

输出格式

输出 N 行,每行一个正整数,第i行整数表示当 X=i 时,阿明最多积累的疲劳值。

输入输出样例

输入样例 #1

5
1 2 3 4 5
1 2 3 4 5

输出样例 #1

15
19
22
24
25

输入样例 #2

5
1 2 2 4 5
5 4 3 4 1

输出样例 #2

12
17
21
24
27

说明/提示

【输入输出样例1说明】

X=1 :向住户 5 推销,往返走路的疲劳值为 5+5 ,推销的疲劳值为 5 ,总疲劳值为 15

X=2 :向住户 4,5 推销,往返走路的疲劳值为 5+5 ,推销的疲劳值为 4+5 ,总疲劳值为 5+5+4+5=19

X=3 :向住户 3,4,5 推销,往返走路的疲劳值为 5+5 ,推销的疲劳值 3+4+5 ,总疲劳值为 5+5+3+4+5=22

X=4 :向住户 2,3,4,5 推销,往返走路的疲劳值为 5+5 ,推销的疲劳值 2+3+4+5 ,总疲劳值 5+5+2+3+4+5=24

X=5 :向住户 1,2,3,4,5 推销,往返走路的疲劳值为 5+5 ,推销的疲劳值 1+2+3+4+5 ,总疲劳值 5+5+1+2+3+4+5=25

【输入输出样例2说明】

X=1 :向住户 4 推销,往返走路的疲劳值为 4+4 ,推销的疲劳值为 4 ,总疲劳值 4+4+4=12

X=2 :向住户 1,4 推销,往返走路的疲劳值为 4+4 ,推销的疲劳值为 5+4 ,总疲劳值 4+4+5+4=17

X=3 :向住户 1,2,4 推销,往返走路的疲劳值为 4+4 ,推销的疲劳值为 5+4+4 ,总疲劳值 4+4+5+4+4=21

X=4 :向住户 1,2,3,4 推销,往返走路的疲劳值为 4+4 ,推销的疲劳值为 5+4+3+4 ,总疲劳值 4+4+5+4+3+4=24 。或者向住户 1,2,4,5 推销,往返走路的疲劳值为 5+5 ,推销的疲劳值为 5+4+4+1 ,总疲劳值 5+5+5+4+4+1=24

X=5 :向住户 1,2,3,4,5 推销,往返走路的疲劳值为 5+5 ,推销的疲劳值为 5+4+3+4+1 ,总疲劳值 5+5+5+4+3+4+1=27

【数据说明】

对于 20\% 的数据, 1≤N≤20

对于 40\% 的数据, 1≤N≤100

对于 60\% 的数据, 1≤N≤1000

对于 100\% 的数据, 1≤N≤100000

NOIP2015 普及组第四题

题解:

​ 本题可以用贪心的策略来完成。

​ 按照疲劳值进行结构体排序,然后设置 t 数组存储 i 之后的距离与疲劳值最大和。

​ 接着,用 sum 枚举 i 之前的前缀和。对于每个 i ,选择一个最大的距离 t\ +\ ( x\ -\ 1 个最大的路程 a ) 或者 x 个最大的路程 a 永远是最优的。因此,输出 max(前 i - 1 的前缀和 + i 后面最大的路程, 前 i 的前缀和 + 2 * 路程) 就可以。

#include <iostream>
#include <algorithm>
using namespace std;
struct node {
    int s, a;
} salesman[100001];
int t[100002], sum[100001], q[100001]; //sum 存储前缀和
bool cmp (node x, node y) {
    return x.a > y.a;
}
int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> salesman[i].s;
    for (int i = 1; i <= n; i++)
        cin >> salesman[i].a;
    sort(salesman + 1, salesman + 1 + n, cmp); //按照疲劳值进行排序
    for (int i = n; i >= 1; i--)
        t[i] = max(t[i + 1], salesman[i].s * 2 + salesman[i].a);
    for (int i = 1; i <= n; i++) {
        sum[i] = sum[i - 1] + salesman[i].a; //前缀和
        q[i] = max(q[i - 1], salesman[i].s); //最大距离
        cout << max(sum[i - 1] + t[i], sum[i] + 2 * q[i]) << endl;
    }
    return 0;
}